/*
 * UWT 2014
 */
package model;

import static com.google.common.base.Preconditions.*;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.javaml.clustering.Clusterer;
import net.sf.javaml.core.Dataset;
import net.sf.javaml.tools.data.FileHandler;

/**
 * @author Jamada
 */
public class HeatMapCluster {
	private static final Logger logger = LoggerFactory.getLogger(HeatMapCluster.class);
	
	private static final String DATA_FILE = "Dataset/current_data_set";
	
	private static final String CLUSTER_FILE = "Dataset/cluster_data_set_";
	
	private static final String CSV_EXT = ".csv";
	
	private static final String TEXT_EXT = ".txt";
	
    /**
     * Used to build strings that are written to a file.
     */
    private static final StringBuilder SB = new StringBuilder();
	private List<String> clusteredFileNames;
	private final HeatMapData heatMapData;
	private Dataset[] clusteredDatasets;
	private List<HeatMapData> finalClusteredHeatMaps;
	private List<HeatMapData> proteinClusteredHeatMaps;
	
	public HeatMapCluster(final HeatMapData heatMap, final Clusterer kMeansPro, final Clusterer kMeansComp) {
		this.heatMapData = heatMap;
		this.finalClusteredHeatMaps = new ArrayList<HeatMapData>();
		if (kMeansPro != null && kMeansComp != null) {
			executeCombinedClustering(kMeansPro, kMeansComp);
		} else if (kMeansPro != null) {
			executeProteinClustering(kMeansPro);
			this.finalClusteredHeatMaps = this.proteinClusteredHeatMaps;
		} else if (kMeansComp != null) {
			executeCompoundClustering(kMeansComp, this.heatMapData);
		} else {
			this.finalClusteredHeatMaps.add(heatMap);
		}
	}
	
	private void executeCombinedClustering(final Clusterer kMeansPro, final Clusterer kMeansComp) {
		executeProteinClustering(kMeansPro);
		for (int i = 0; i < this.proteinClusteredHeatMaps.size(); i++) {
			executeCompoundClustering(kMeansComp, this.proteinClusteredHeatMaps.get(i));
		}
	}
	
	private void executeProteinClustering(final Clusterer kMeansPro) {
		logger.info("Clustering data by proteins...");
		this.clusteredDatasets = kMeansPro.cluster(checkNotNull(generateProteinDataset()));
		this.clusteredFileNames = writeClustersToFile();
		this.proteinClusteredHeatMaps = createClusteredHeatMapData();
	}
	
	private void executeCompoundClustering(final Clusterer kMeansComp, final HeatMapData mapData) {
		this.clusteredDatasets = kMeansComp.cluster(checkNotNull(generateCompoundDataset(mapData)));
		this.clusteredFileNames = writeClustersToFile();
		final List<HeatMapData> heatMapList = createCompoundClusteredHeatMapData(mapData);
		for (int i = 0; i < heatMapList.size(); i++) {
			this.finalClusteredHeatMaps.add(heatMapList.get(i));
		}
	}
	
	public List<HeatMapData> getClusteredHeatMapData() {
		return this.finalClusteredHeatMaps;
	}
	
	
	public List<HeatMapData> getRankedClusters() {
		final List<HeatMapData> rankedClusters = new ArrayList<HeatMapData>();
		final Map<Double, Integer> signalClusterMap = new HashMap<Double, Integer>();
		final List<Double> signalList = new ArrayList<Double>();
		logger.info("Ranking {} clusters", this.finalClusteredHeatMaps.size());
		for (int i = 0; i < this.finalClusteredHeatMaps.size(); i ++) {
			final double signal = this.finalClusteredHeatMaps.get(i).getAvgSignal();
			signalClusterMap.put(signal, i);
			signalList.add(signal);
		}
		Collections.sort(signalList);
		if (signalList.size() > 100) {
			for (int i = signalList.size() - 1; i >= signalList.size() - 100; i--) {
				rankedClusters.add(this.finalClusteredHeatMaps.get(signalClusterMap.get(signalList.get(i))));
			}
		} else {
			for (int i = signalList.size() - 1; i >= 0; i--) {
				rankedClusters.add(this.finalClusteredHeatMaps.get(signalClusterMap.get(signalList.get(i))));
			}
		}
		logger.info("Ranking of clusters complete.");
		return rankedClusters;
	}
	
	private List<HeatMapData> createClusteredHeatMapData() {
		final List<HeatMapData> clusteredData = new ArrayList<HeatMapData>();
		final Object[] proteinIDs = this.heatMapData.getYValues();
		try {
			for (int i = 0; i < this.clusteredFileNames.size(); i ++) {
				BufferedReader bReader = new BufferedReader(new FileReader(this.clusteredFileNames.get(i)));	
				List<Integer> proteinIndexList = new ArrayList<Integer>();
				List<double[]> dataList = new ArrayList<double[]>();
				String line;
				while ((line = bReader.readLine()) != null) {
					String values[] = line.split("\t");
					proteinIndexList.add(Integer.parseInt(values[0]));
					final double[] dataRow = new double[this.heatMapData.getXValues().length];
					for (int j = 1; j < values.length; j++) {
						dataRow[j - 1] = Double.parseDouble(values[j]);
					}
					dataList.add(dataRow);
				}
				bReader.close();
				final double[][] completeData = new double[dataList.size()][this.heatMapData.getXValues().length];
				final Object[] clusteredProteinIDs = new Object[dataList.size()];
				for (int j = 0; j < dataList.size(); j++) {
					clusteredProteinIDs[j] = proteinIDs[proteinIndexList.get(j)];
					final double[] dataRow = dataList.get(j);
					for (int k = 0; k < dataRow.length; k++) {
						completeData[j][k] = dataRow[k];
					}
				}
				final HeatMapData mapData = new HeatMapData(completeData, this.heatMapData.getXValues(), clusteredProteinIDs);
				mapData.setLowColor(this.heatMapData.getLowColor());
				mapData.setHighColor(this.heatMapData.getHighColor());
				clusteredData.add(mapData);
			}
		} catch (IOException e) {

		}
		return clusteredData;		
	}
	
	private List<HeatMapData> createCompoundClusteredHeatMapData(final HeatMapData mapData) {
		final List<HeatMapData> clusteredData = new ArrayList<HeatMapData>();
		final Object[] compoundNames = mapData.getXValues();
		try {
			for (int i = 0; i < this.clusteredFileNames.size(); i ++) {
				logger.info("Reading from file: {}", this.clusteredFileNames.get(i));
				BufferedReader bReader = new BufferedReader(new FileReader(this.clusteredFileNames.get(i)));	
				List<Integer> compoundIndexList = new ArrayList<Integer>();
				List<double[]> dataList = new ArrayList<double[]>();
				String line;
				while ((line = bReader.readLine()) != null) {
					String values[] = line.split("\t");
					compoundIndexList.add(Integer.parseInt(values[0]));
					final double[] dataRow = new double[mapData.getYValues().length];
					for (int j = 1; j < values.length; j++) {
						dataRow[j - 1] = Double.parseDouble(values[j]);
					}
					dataList.add(dataRow);
				}
				bReader.close();
				logger.info("File reading complete. Creating heat map data...");
				final double[][] completeData = new double[mapData.getYValues().length][dataList.size()];
				final Object[] clusteredCompoundNames = new Object[dataList.size()];
				for (int j = 0; j < dataList.size(); j++) {
					clusteredCompoundNames[j] = compoundNames[compoundIndexList.get(j)];
					final double[] dataRow = dataList.get(j);
					for (int k = 0; k < dataRow.length; k++) {
						completeData[k][j] = dataRow[k];
					}
				}
				final HeatMapData mapDataResult = new HeatMapData(completeData, clusteredCompoundNames, mapData.getYValues());
				mapDataResult.setLowColor(mapData.getLowColor());
				mapDataResult.setHighColor(mapData.getHighColor());
				clusteredData.add(mapDataResult);
			}
		} catch (IOException e) {
			logger.error("IO exception creating compound cluster heat map data: {}", e);
		}
		logger.info("Heat map data created successfully. Total clusters: {}", clusteredData.size());
		return clusteredData;
	}
	
	private List<String> writeClustersToFile() {
		final List<String> clusterFiles = new ArrayList<String>();
		for (int i = 0; i < this.clusteredDatasets.length; i++) {
			final String fileName = CLUSTER_FILE + (i + 1) + TEXT_EXT;
			clusterFiles.add(fileName);
			logger.info("Exporting cluster {} to file: {}", i+1, fileName);
			try {
				FileHandler.exportDataset(this.clusteredDatasets[i], new File(fileName));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return clusterFiles;
	}
	
	private Dataset generateProteinDataset() {
		final String fileName = DATA_FILE + CSV_EXT;
		try {
			BufferedWriter bWriter = new BufferedWriter(new FileWriter(fileName));
			logger.info("Writing data to file {}", fileName);
			
			//Write data entries to file.
			final Object[] compoundNames = this.heatMapData.getXValues();			
			final double[][] data = this.heatMapData.getData();
			logger.info("Data length: {}", data.length);
			for (int i = 0; i < data.length; i ++) {
				SB.append(i + ", " + data[i][0]);
				for (int j = 1; j < compoundNames.length; j++) {
					SB.append(", " + data[i][j]);
				}
				SB.append("\n");
				bWriter.write(SB.toString());
				SB.setLength(0);
			}
			bWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		Dataset result = null;
		try {
			result = FileHandler.loadDataset(new File(fileName), 0,  ",");
			logger.info("Loaded dataset from file: {}", fileName);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}	
	
	private Dataset generateCompoundDataset(final HeatMapData mapData) {
		final String fileName = DATA_FILE + CSV_EXT;
		try {
			BufferedWriter bWriter = new BufferedWriter(new FileWriter(fileName));
			logger.info("Writing data to file {}", fileName);
			
			//Write data entries to file.
			final Object[] compoundNames = mapData.getXValues();			
			final double[][] data = mapData.getData();
			for (int i = 0; i < compoundNames.length; i ++) {
				SB.append(i);
				for (int j = 0; j < data.length; j++) {
					SB.append(", " + data[j][i]);
				}
				SB.append("\n");
				bWriter.write(SB.toString());
				SB.setLength(0);
			}
			bWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		Dataset result = null;
		try {
			result = FileHandler.loadDataset(new File(fileName), 0,  ",");
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}	
	
}
